package controller

import (
	"bytes"
	"context"
	"errors"
	"gitee.com/Frank098/container-management/model"
	"gitee.com/Frank098/container-management/utils"
	"gitee.com/Frank098/container-management/utils/log"
	"github.com/gin-gonic/gin"
	"io"
	"net/http"
	"strings"
	"time"
)

const (
	RESERROR = "RESERROR"
)

func TimeoutMiddleware(timeout time.Duration) func(ctx *gin.Context) {
	return func(ctx *gin.Context) {

		// wrap the request context with a timeout
		c, cancel := context.WithTimeout(ctx.Request.Context(), timeout)

		defer func() {
			// check if context timeout was reached
			if c.Err() == context.DeadlineExceeded {

				// write response and abort the request
				ctx.Writer.WriteHeader(http.StatusGatewayTimeout)
				ctx.Abort()
			}

			//cancel to clear resources after finished
			cancel()
		}()

		// replace request with context wrapped request
		ctx.Request = ctx.Request.WithContext(ctx)
		ctx.Next()
	}
}

func ErrorResponse(ctx *gin.Context, err error, code ...int) {
	err = Translate(err)
	ctx.Set(RESERROR, err)
	obj := &model.Result{
		Msg: err.Error(),
	}
	if len(code) >= 1 {
		obj.Code = code[0]
	}
	ctx.JSON(http.StatusBadRequest, obj)
	return
}

func SuccessResponse(ctx *gin.Context, obj interface{}) {
	if utils.IsNil(obj) {
		obj = &model.Result{}
	}
	ctx.JSON(http.StatusOK, obj)
	return
}

type bodyLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

// HandlerInfo 自定义gin路由日志
// 注意，这里每个handler应该只做一次日志打印，在并发情况下会打串了
type HandlerInfo struct {
	Id          string    `json:"id"`
	LatencyTime float64   `json:"latency_time"`
	Request     *Request  `json:"request"`
	Response    *Response `json:"response"`
}
type Request struct {
	Header *RequestHeader `json:"header"`
	Body   JsonRawString  `json:"body"`
}
type Response struct {
	Header *ResponseHeader `json:"header"`
	Body   JsonRawString   `json:"body"`
}

type JsonRawString string

func (m JsonRawString) MarshalJSON() ([]byte, error) {
	if len(m) == 0 {
		return []byte("null"), nil
	}
	return []byte(m), nil
}

type RequestHeader struct {
	Method      string `json:"method"`
	RequestURI  string `json:"request_uri"`
	ContentType string `json:"content_type"`
	ClientIP    string `json:"client_ip"`
	UserAgent   string `json:"user_agent"`
}
type ResponseHeader struct {
	Status int `json:"status"`
}

func Logger() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 打印路由日志
		startTime := time.Now()
		reqMethod := ctx.Request.Method
		reqURI := ctx.Request.RequestURI
		reqBody, err := io.ReadAll(ctx.Request.Body)
		if err != nil {
			log.Errorf("%+v", err)
		}
		ctx.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer}
		ctx.Writer = blw
		ctx.Next()
		statusCode := ctx.Writer.Status()
		latencyTime := time.Since(startTime)

		var id = utils.GetValidateCode(8)
		handlerInfo := &HandlerInfo{
			Id:          id,
			LatencyTime: float64(latencyTime) / 1000 / 1000,
			Request: &Request{
				Header: &RequestHeader{
					Method:      reqMethod,
					RequestURI:  reqURI,
					ContentType: ctx.ContentType(),
					ClientIP:    ctx.ClientIP(),
					UserAgent:   ctx.Request.UserAgent(),
				},
				Body: JsonRawString(reqBody),
			},
			Response: &Response{
				Header: &ResponseHeader{
					Status: statusCode,
				},
				Body: JsonRawString(blw.body.Bytes()),
			},
		}
		if strings.Contains(ctx.Writer.Header().Get("Content-Type"), "application/json") {
			if ctx.Writer.Status() < 400 {
				log.InfoJson(handlerInfo)
			} else if ctx.Writer.Status() >= 400 {
				err, _ := ctx.Get(RESERROR)
				log.ErrorJson(handlerInfo)
				log.Errorf("handler id is %s %+v ,", id, err)
			}
		} else {
			if ctx.Writer.Status() < 400 {
				log.InfoJson(handlerInfo.Request.Header)
			} else if ctx.Writer.Status() >= 400 {
				log.ErrorJson(handlerInfo)
			}
		}
	}
}

func CheckContentType(ctx *gin.Context) {
	contentType := ctx.ContentType()
	if contentType == "" || strings.Contains(contentType, "application/json") {
		ctx.Next()
		return
	}
	ErrorResponse(ctx, errors.New("content-type错误"))
	ctx.Abort()
}
